home *** CD-ROM | disk | FTP | other *** search
/ BBS in a Box 3 / BBS in a box - Trilogy III.iso / Files / Tele / Pete Johnson / Ff 1.5 source Folder / ff.p < prev    next >
Encoding:
Text File  |  1991-08-05  |  29.8 KB  |  1,036 lines  |  [TEXT/PJMM]

  1. program ff;
  2.  
  3. {    Forwards local and net private mail to net addresses entered in    }
  4. {    a text file called 'ff List'                                }
  5. {    Format for text file is as follows:                        }
  6. {    FirstName <space> LastName <tab> NodeID <return>                }
  7. {    Lines beginning with open parens are ignored and can be used as    }
  8. {    comments.                                                }
  9. {    Can handle a maximum of 200 names and node numbers.            }
  10. {    STRs:                                                }
  11. {        499 -- private message mark (^P)                        }
  12. {        500 -- next launch                                    }
  13. {        502 -- defaults xx                                    }
  14. {                                                        }
  15. {    Written by Pete Johnson beginning Feb. 24, 1990.                }
  16. {    3/10/90    V 1.3    Reads from old to new                    }
  17. {    3/16/90    V 1.4    Fixes path problem with EMS & Sendfiles        }
  18. {    3/23/90    V 1.5    Added check to ignore deleted message        }
  19. {    3/23/90    V 1.51    Fixed check for deleted message            }
  20. {    3/30/90    V 1.0    Name changed to ff, added ID sig to messages    }
  21. {    3/31/90    V 1.01    Writes EMS files sent to AreaTrix Workfile    }
  22. {    4/1/90    V 1.02    Adds extra CR before signature            }
  23. {    4/21/90    V 1.1    Creates proper sendmailxxx/yyy.bbs packets    }
  24. {    5/5/90    V 1.2    Tags local private messages with PRIVMARK at    }
  25. {                    end of From (preferred) or Subject line        }
  26. {    5/12/90    V 1.3    Handles point addressing correctly            }
  27. {    6/16/90    V 1.4    Uses "processed" flag instead of message no.    }
  28. {    6/23/90    V 1.41    Fixes mangled Sendmail7500/.bbs names        }
  29. {    12/8/90    V 1.42    Works correctly with passwords            }
  30. {    12/28/90    V 1.43    Sigh, fixes bug with passwords            }
  31. {    1/19/91    V 1.44    Adds Delete Message toggle, works on garbage    }
  32. {                    in node number bug                        }
  33. {    1/21/91    V 1.45    Fixes garbage in node number bug -- this was    }
  34. {                    caused by not filtering out point numbers    }
  35. {                    from incoming messages. Also eliminated        }
  36. {                    double ff tags                            }
  37. {    2/5/91    V 1.47    Handles 'McName' correctly.                }
  38. {    5/29/91    V 1.48    Added SystemTasks & SIZE resource.            }
  39. {    7/14/91    V 1.49    Refined routine to find place in messages.    }
  40. {    7/18/91    V 1.5    Fixed problem with origin node ID & added    }
  41. {                    color icons.                            }
  42.  
  43. uses
  44.     HelloTabby;
  45.  
  46. const
  47.     VERSION = '1.5';
  48.     TabbyFlag = 64;
  49.     TAB = chr(9);
  50.     ENDLINE = chr(13);
  51.     SPACE = chr(32);
  52.     PAREN = '(';
  53.     IGNORE = 0;
  54.     LOCALPRIV = 1;
  55.     NETPRIV = 3;
  56.     NULL = chr(0);
  57.     CTLA = chr(1);
  58.     ADDRESSFILE = 'ff List';
  59.     MAXNAMES = 200;
  60.     DEBUG = false;    {if true, writes extra info -- search for 'debug' to find}
  61.  
  62. type
  63.     Person = record
  64.             Name: string[32];
  65.             Location: string[16];
  66.         end;        {    Person record    }
  67.     PersonPtr = ^Person;
  68.     PersonHdl = ^PersonPtr;
  69.     Address = array[1..MAXNAMES] of PersonHdl;
  70.  
  71. var
  72.     TheAddress: Address;
  73.     LastEntry, CurrentResFile: integer;
  74.     NetPrivSect: byte;
  75.     MESSAGESPath, MsgPath, TempString, PointNet, GenericPath, LocalNodeID, Defaults: str255;
  76.     TempFrom, TempSubj, PrivMark: str255;
  77.     LowMsg, HiMsg, MSGTXTLength: string;
  78.     LowMsgInt, HiMsgInt, MSGTXTLengthInt: longint;
  79.     OrigNode, OrigNet, DestNode, DestNet: longint;
  80.     MsgCategory: array[1..255] of integer;
  81.     DialogPointer: DialogPtr;
  82.     DoLocPriv, DoNetPriv: boolean;
  83.  
  84. {-----------------------------------------------------------------    }
  85.  
  86. procedure DeCap (var TheName: str255);
  87.  
  88.     var
  89.         NameCount: integer;
  90.  
  91.     procedure HandleMcName (var McN: str255);    {Adjusts caps in names such as McNamara}
  92.  
  93.         var
  94.             i: integer;
  95.  
  96.         begin
  97.             if (length(McN) > 2) then
  98.                 for i := 3 to length(McN) do
  99.                     if ((McN[i - 1] = 'c') & (McN[i - 2] = 'M') & (McN[i] in ['a'..'z'])) & ((i = 3) | (McN[i - 3] = ' ')) then
  100.                         McN[i] := chr(ord(McN[i]) - 32);
  101.         end;
  102.  
  103.     begin
  104.         UprString(TheName, false);
  105.         for NameCount := 2 to length(TheName) do        {    Convert name to caps & lower case    }
  106.             if (TheName[NameCount]) in ['A'..'Z'] then
  107.                 if (TheName[NameCount - 1] in ['A'..'Z', 'a'..'z']) then
  108.                     TheName[NameCount] := chr(ord(TheName[NameCount]) + 32);
  109.  
  110.         HandleMcName(TheName)
  111.     end;
  112.  
  113. {-----------------------------------------------------------------    }
  114.  
  115. function AtEOF (fRefNum: Integer): Boolean;
  116.     var
  117.         currPos, eofPos: LongInt;
  118.  
  119.     begin
  120.         Err := GetFPos(fRefNum, currPos);
  121.         Err := GetEOF(fRefNum, eofPos);
  122.         AtEOF := currPos = eofPos
  123.     end;
  124.  
  125. { ------------------------------------------------------ }
  126.  
  127. function Wr (FileRefNum: integer; TheMessage: string): OSErr;
  128.  
  129. {    Writes string (without length byte) to text file, returns error code    }
  130.  
  131.     var
  132.         TheLength: longint;
  133.  
  134.     begin
  135.         TheLength := length(TheMessage);
  136.         Wr := FSWrite(FileRefNum, TheLength, Pointer(ord(@TheMessage) + 1));
  137.     end;
  138.  
  139. {-----------------------------------------------------------------    }
  140.  
  141. function WrLn (FileRefNum: integer; TheMessage: string): OSErr;
  142.  
  143. {    Writes string (without length byte) to text file, returns error code    }
  144.  
  145.     begin
  146.         TheMessage := concat(TheMessage, ENDLINE);
  147.         WrLn := Wr(FileRefNum, TheMessage);
  148.     end;
  149.  
  150. {-----------------------------------------------------------------    }
  151.  
  152. procedure FrameDItem (dLog: DialogPtr; iNum: integer);
  153.  
  154.     var
  155.         iBox: Rect;
  156.         iType: integer;
  157.         iHandle: Handle;
  158.         oldPenState: PenState;
  159.  
  160.     begin
  161.         GetPenState(oldPenState);
  162.         GetDItem(dLog, iNum, iType, iHandle, iBox);
  163.         InsetRect(iBox, -4, -4);
  164.         PenSize(3, 3);
  165.         FrameRoundRect(iBox, 16, 16);
  166.         SetPenState(oldPenState)
  167.     end;
  168.  
  169. {-----------------------------------------------------------------    }
  170.  
  171. procedure MakeTextFile (FileName: STR255);
  172.  
  173. {    Sets up QUED-compatible text  file                    }
  174.  
  175.     var
  176.         fndrInfo: FInfo;
  177.  
  178.     begin
  179.         Err := GetFInfo(FileName, vRefNum, fndrInfo);
  180.         if Err = noErr then
  181.             begin
  182.                 fndrInfo.fdType := 'TEXT';
  183.                 fndrInfo.fdCreator := 'QED1';
  184.                 Err := SetFInfo(FileName, vRefNum, fndrInfo);
  185.             end
  186.         else
  187.             Err := Create(FileName, vRefNum, 'QED1', 'TEXT');
  188.     end;
  189.  
  190. {-----------------------------------------------------------------    }
  191.  
  192. function ButtonSelected (whichDialog: DialogPtr; whichItem: integer): boolean;
  193.  
  194.     var
  195.         whichType: integer;
  196.         whichHandle: handle;
  197.         whichRect, displayRect: rect;
  198.         mouseLoc: point;
  199.         DelayTime: longint;
  200.         nowInverted: boolean;
  201.  
  202.     begin
  203.         getDItem(whichDialog, whichItem, whichType, whichHandle, whichRect);
  204.         displayRect := whichRect;
  205.         InsetRect(displayRect, 1, 1);
  206.         InvertRect(displayRect);
  207.         nowInverted := true;
  208.         if StillDown then
  209.             repeat
  210.                 GetMouse(mouseLoc);
  211.                 if PtInRect(mouseLoc, whichRect) then
  212.                     begin
  213.                         if not nowInverted then
  214.                             begin
  215.                                 InvertRect(displayRect);
  216.                                 nowInverted := true
  217.                             end
  218.                     end
  219.                 else
  220.                     begin
  221.                         if nowInverted then
  222.                             begin
  223.                                 InvertRect(displayRect);
  224.                                 nowInverted := false
  225.                             end
  226.                     end
  227.             until not StillDown;
  228.         if nowInverted then
  229.             begin
  230.                 Delay(4, DelayTime);
  231.                 InvertRect(displayRect);
  232.             end;
  233.         ButtonSelected := nowInverted
  234.     end;
  235.  
  236. {-----------------------------------------------------------------    }
  237.  
  238. procedure ReadConfig;
  239.  
  240. {    Reads Config file and returns Path:MESSAGES                    }
  241.  
  242.     var
  243.         AString: string;
  244.         ConfigRefNum: integer;
  245.         CharsToSend: longint;
  246.  
  247.     begin
  248.         MESSAGESPath := '';
  249.         Err := FSOpen(concat(gDefaultpath, 'Config'), VRefNum, ConfigRefNum);
  250.         if (Err = NoErr) then
  251.             begin
  252.                 CharsToSend := 80;
  253.                 Err := SetFPos(ConfigRefNum, fsFromStart, 139);
  254.                 Err := FSRead(ConfigRefNum, CharsToSend, @AString);
  255.                 if length(AString) > 0 then
  256.                     MESSAGESPath := AString;
  257.                 MESSAGESPath := concat(MESSAGESPath, ':MESSAGES');
  258.             end;    {    Error on open Config    }
  259.         Err := FSClose(ConfigRefNum);
  260.     end;
  261.  
  262. {-----------------------------------------------------------------    }
  263.  
  264. procedure ReadMESSAGES;
  265.  
  266. { Reads the MESSAGES file                                    }
  267.  
  268.     var
  269.         MSGRefNum, MSCount, Counter: integer;
  270.         CharsToSend: longint;
  271.         MsgByte: byte;
  272.  
  273.     begin
  274.         Counter := 0;
  275.         NetPrivSect := 50;
  276.         Err := FSOpen(MESSAGESPath, VRefNum, MSGRefNum);
  277.  
  278.         CharsToSend := 50;
  279.         Err := FSRead(MSGRefNum, CharsToSend, @MsgPath);
  280.         if MsgPath <> '' then
  281.             MsgPath := concat(MsgPath, ':');
  282.  
  283.         CharsToSend := 4;
  284.         Err := SetFPos(MSGRefNum, fsFromStart, 50);
  285.         Err := FSRead(MSGRefNum, CharsToSend, @LowMsg);
  286.         Err := FSRead(MSGRefNum, CharsToSend, @HiMsg);
  287.         Err := FSRead(MSGRefNum, CharsToSend, @MSGTXTLength);
  288.  
  289.         StringToNum(LowMsg, LowMsgInt);
  290.         StringToNum(HiMsg, HiMsgInt);
  291.         StringToNum(MSGTXTLength, MSGTXTLengthInt);
  292.  
  293.         for MSCount := 1 to 255 do
  294.             begin
  295.                 Err := SetFPos(MSGRefNum, fsFromStart, (97 + (MSCount - 1) * 36));
  296.                 MsgByte := 0;
  297.                 CharsToSend := 1;
  298.                 Err := FSRead(MSGRefNum, CharsToSend, @MsgByte);
  299.  
  300.                 MsgByte := MsgByte div 256;
  301.  
  302.                 case MsgByte of
  303.  
  304.                     NETPRIV: 
  305.                         begin
  306.                             MsgCategory[MSCount] := NETPRIV;
  307.                             NetPrivSect := MSCount
  308.                         end;
  309.  
  310.                     LOCALPRIV: 
  311.                         MsgCategory[MSCount] := LOCALPRIV;
  312.  
  313.                     otherwise
  314.                         MsgCategory[MSCount] := IGNORE;
  315.  
  316.                 end;    {    case statement    }
  317.  
  318.             end;        {    for MSCount := 1 to 255 do    }
  319.  
  320.         Err := FSClose(MSGRefNum);
  321.     end;
  322.  
  323. {-----------------------------------------------------------------    }
  324.  
  325. procedure CleanString (var TheString: str255);
  326.  
  327.     begin
  328.         while (TheString[1] in [SPACE, TAB]) & (length(TheString) > 1) do
  329.             TheString := copy(TheString, 2, 255);
  330.         while (TheString[length(TheString)] in [SPACE, TAB]) & (length(TheString) > 1) do
  331.             TheString := copy(TheString, 1, length(TheString) - 1)
  332.     end;
  333.  
  334. {-----------------------------------------------------------------    }
  335.  
  336. procedure ReadSettings;
  337.  
  338.     var
  339.         AddressRef, Counter, TabMark, StrCount: integer;
  340.         Entry, TempStr, TempStr2: str255;
  341.  
  342.     begin
  343.         Counter := 1;
  344.         Err := FSOpen(AddressFile, vRefNum, AddressRef);
  345.         if Err = NoErr then
  346.             Err := SetFPos(AddressRef, fsFromStart, 0);
  347.         if Err = NoErr then
  348.             while (not AtEOF(AddressRef)) & (Counter <= MAXNAMES) do
  349.                 begin
  350.                     Err := ReadALine(AddressRef, Entry);
  351.                     if Err = NoErr then
  352.                         begin
  353.                             TabMark := pos(TAB, Entry);
  354.                             if (TabMark > 0) & (pos(PAREN, Entry) <> 1) then
  355.                                 begin
  356.                                     TheAddress[Counter] := PersonHdl(NewHandle(SizeOf(Person)));
  357.                                     TempStr := copy(Entry, 1, TabMark - 1);
  358.                                     CleanString(TempStr);
  359.                                     TheAddress[Counter]^^.Name := TempStr;
  360.                                     TempStr := copy(Entry, TabMark + 1, 20);
  361.                                     StrCount := 1;
  362.                                     TempStr2 := '';
  363.                                     while ((TempStr[StrCount] in ['0'..'9']) | (TempStr[StrCount] = '/') | (TempStr[StrCount] = '.') | (TempStr[StrCount] = ':')) & (StrCount <= length(TempStr)) do
  364.                                         begin
  365.                                             TempStr2 := concat(TempStr2, TempStr[StrCount]);
  366.                                             StrCount := succ(StrCount);
  367.                                         end;
  368.                                     TheAddress[Counter]^^.Location := TempStr2;
  369.                                     Counter := succ(Counter);
  370.                                 end
  371.                         end
  372.                 end;
  373.         LastEntry := Counter - 1;
  374.         Err := FSClose(AddressRef)
  375.     end;
  376.  
  377. {-----------------------------------------------------------------    }
  378.  
  379. procedure ProcessHeaders;
  380.  
  381.     const
  382.         Active = 1;
  383.         Deleted = 1;
  384.         Undeleted = 0;
  385.  
  386.     type
  387.         DateTimeRecord = packed array[1..6] of char;
  388.         Header = record
  389.                 Status: packed array[1..2] of Byte;    {    use Status[1]    }
  390.                 MsgNo: longint;
  391.                 Section: packed array[1..2] of Byte;    {    use Section[1]    }
  392.                 TimeRcvd: DateTimeRecord;
  393.                 MsgFrom: string[31];
  394.                 MsgTo: string[31];
  395.                 MsgSubject: string[41];
  396.                 Dest: string[67];
  397.                 BeginText: longint;
  398.                 LengthText: longint;
  399.                 ReplyTo: longint;
  400.                 TimeSent: DateTimeRecord
  401.             end;        {    Header record    }
  402.         MText = packed array[1..32000] of char;
  403.         MTextPtr = ^MText;
  404.         MTextHandle = ^MTextPtr;
  405.         PacketHeader = packed array[0..57] of byte;
  406.  
  407.     var
  408.         MHdrRef, Counter, UserCount, SendRef, MSGTXTRef, CompressRef, Count, PWRef: integer;
  409.         NextCount, TheStatus: integer;
  410.         TheHeader: Header;
  411.         HeaderSize, CharsToSend, logicalEOF, TempLong, MSGTXTPos: longint;
  412.         TempTo, TempNode, TempFileName, OneLine, NodeString, TempTime, Password: str255;
  413.         TheTextHandle: MTextHandle;
  414.  
  415. { -----------------------------------------------------}
  416.  
  417.     procedure FindMHPosition;
  418.  
  419.         var
  420.             HiBound, LoBound, HeaderEnd, Position: longint;
  421.  
  422. { Procedure finds correct position in MSGHDR file                    }
  423.  
  424.         begin
  425.             Err := GetEOF(MHdrRef, HeaderEnd);
  426.             HiBound := (HeaderEnd div HeaderSize) - 1;    {    ...mark start of last record        }
  427.             LoBound := 0;
  428.             repeat
  429.                 Position := (LoBound + HiBound) div 2;
  430.                 if Err = NoErr then
  431.                     Err := SetFPos(MHdrRef, fsFromStart, Position * HeaderSize);
  432.                 if Err = NoErr then
  433.                     Err := FSRead(MHdrRef, HeaderSize, @TheHeader);
  434.                 if Err = NoErr then
  435.                     if (BitAnd(TabbyFlag, TheHeader.Status[1]) = TabbyFlag) then    {processed for Tabby}
  436.                         LoBound := Position + 1
  437.                     else
  438.                         HiBound := Position - 1
  439.                 else {file errors}
  440.                     Position := 0
  441.             until (LoBound > HiBound) | (Err <> NoErr);
  442.     {back up a bit just to be sure}
  443.             while (BitAnd(TabbyFlag, TheHeader.Status[1]) <> TabbyFlag) & (Err = NoErr) & (Position > 0) do
  444.                 begin
  445.                     Position := pred(Position);
  446.                     Err := SetFPos(MHdrRef, fsFromStart, Position * HeaderSize);
  447.                     Err := FSRead(MHdrRef, HeaderSize, @TheHeader);
  448.                 end;
  449.         end;        {    procedure FindMHPosition    }
  450.  
  451. { ------------------------------------------------------ }
  452.  
  453.     function MakeTime (Index: integer; Separator: char; WhenRcvdString: DateTimeRecord): string;
  454.  
  455. { Function changes three chars of DateTimeRecord to formatted time or date string    }
  456.  
  457.         var
  458.             MakeTimeString, LocalTemp: STR255;
  459.             OneChar: char;
  460.  
  461.         begin
  462.             LocalTemp := '';
  463.             if Separator = ' ' then        {    Need to swap bytes 1&2 of RRH date        }
  464.                 begin                    {    record to put into proper Fido order.    }
  465.                     OneChar := WhenRcvdString[1];
  466.                     WhenRcvdString[1] := WhenRcvdString[2];
  467.                     WhenRcvdString[2] := OneChar
  468.                 end;
  469.             NumToString(ord(WhenRcvdString[Index + 1]), LocalTemp);
  470.             if length(LocalTemp) = 1 then
  471.                 LocalTemp := concat('0', LocalTemp);
  472.             MakeTimeString := concat(LocalTemp, Separator);
  473.             if Separator = ':' then
  474.                 begin
  475.                     NumToString(ord(WhenRcvdString[Index + 2]), LocalTemp);
  476.                     if length(LocalTemp) = 1 then
  477.                         LocalTemp := concat('0', LocalTemp);
  478.                 end
  479.             else
  480.                 case ord(WhenRcvdString[Index + 2]) of
  481.  
  482.                     1: 
  483.                         LocalTemp := 'Jan';
  484.  
  485.                     2: 
  486.                         LocalTemp := 'Feb';
  487.  
  488.                     3: 
  489.                         LocalTemp := 'Mar';
  490.  
  491.                     4: 
  492.                         LocalTemp := 'Apr';
  493.  
  494.                     5: 
  495.                         LocalTemp := 'May';
  496.  
  497.                     6: 
  498.                         LocalTemp := 'Jun';
  499.  
  500.                     7: 
  501.                         LocalTemp := 'Jul';
  502.  
  503.                     8: 
  504.                         LocalTemp := 'Aug';
  505.  
  506.                     9: 
  507.                         LocalTemp := 'Sep';
  508.  
  509.                     10: 
  510.                         LocalTemp := 'Oct';
  511.  
  512.                     11: 
  513.                         LocalTemp := 'Nov';
  514.  
  515.                     otherwise
  516.                         LocalTemp := 'Dec'
  517.  
  518.                 end;        {    case statement    }
  519.             MakeTimeString := concat(MakeTimeString, LocalTemp, Separator);
  520.             NumToString(ord(WhenRcvdString[Index + 3]), LocalTemp);
  521.             if length(LocalTemp) = 1 then
  522.                 LocalTemp := concat('0', LocalTemp);
  523.             MakeTime := concat(MakeTimeString, LocalTemp)
  524.         end;
  525.  
  526. {-----------------------------------------------------------------    }
  527.  
  528.     procedure WritePacketHeader (Dest: str255; FileRef: integer);
  529.  
  530.         var
  531.             PHeader: PacketHeader;
  532.             NowSecs, TheLength: longint;
  533.             Now: DateTimeRec;
  534.             Counter: integer;
  535.  
  536.         begin
  537.             GetDateTime(NowSecs);
  538.             Secs2Date(NowSecs, Now);
  539.  
  540.             if pos('/', Dest) > 0 then
  541.                 begin
  542.                     StringToNum(copy(Dest, pos('/', Dest) + 1, 255), DestNode);
  543.                     StringToNum(copy(Dest, 1, pos('/', Dest) - 1), DestNet);
  544.  
  545.                     PHeader[0] := OrigNode mod 256;
  546.                     PHeader[1] := OrigNode div 256;
  547.                     PHeader[2] := DestNode mod 256;
  548.                     PHeader[3] := DestNode div 256;
  549.                     PHeader[4] := Now.Year mod 256;
  550.                     PHeader[5] := Now.Year div 256;
  551.                     PHeader[6] := Now.Month mod 256;
  552.                     PHeader[7] := 0;
  553.                     PHeader[8] := Now.Day mod 256;
  554.                     PHeader[9] := 0;
  555.                     PHeader[10] := Now.Hour mod 256;
  556.                     PHeader[11] := 0;
  557.                     PHeader[12] := Now.Minute mod 256;
  558.                     PHeader[13] := 0;
  559.                     PHeader[14] := Now.Second mod 256;
  560.                     PHeader[15] := 0;
  561.                     PHeader[16] := 0;            {    Baud rate    }
  562.                     PHeader[17] := 0;            {    Baud rate    }
  563.                     PHeader[18] := 2;            {    Version    }
  564.                     PHeader[19] := 0;            {    Version    }
  565.                     PHeader[20] := OrigNet mod 256;
  566.                     PHeader[21] := OrigNet div 256;
  567.                     PHeader[22] := DestNet mod 256;
  568.                     PHeader[23] := DestNet div 256;
  569.                     PHeader[24] := 8;            {    Tabby product code    }
  570.                     PHeader[25] := 2;            {    Tabby product code    }
  571.                     for Counter := 26 to 56 do
  572.                         PHeader[Counter] := 0;        {    Filler            }
  573.                     if (length(Password) > 0) then
  574.                         for Counter := 26 to (25 + length(Password)) do
  575.                             PHeader[Counter] := ord(Password[Counter - 25]) mod 256;
  576.                     PHeader[34] := 1;            {    Tabby junk???        }
  577.                     PHeader[36] := 1;            {    Tabby junk???        }
  578.                     PHeader[57] := 25;            {    Tabby junk???        }
  579.                     TheLength := 58;
  580.  
  581.                     Err := FSWrite(FileRef, TheLength, @PHeader);
  582.                 end        {    if pos('/', Dest) > 0    }
  583.         end;
  584.  
  585. {-----------------------------------------------------------------    }
  586.  
  587.     procedure WriteMessageTop (MDest, MOrig, MDate, MTo, MFrom, MSub: str255; FileRef: integer);
  588.  
  589.         var
  590.             TheTop: str255;
  591.             MDestNode, MDestNet, MOrigNode, MOrigNet, TheLength: longint;
  592.  
  593.         begin
  594.             if (pos('.', MDest) > 1) then
  595.                 MDest := copy(MDest, 1, pos('.', MDest) - 1);
  596.             if (pos('.', MOrig) > 1) then
  597.                 MOrig := copy(MOrig, 1, pos('.', MOrig) - 1);
  598.             if pos('/', MDest) > 1 then
  599.                 begin
  600.                     TheTop := '';
  601.                     StringToNum(copy(MDest, pos('/', MDest) + 1, 255), MDestNode);
  602.                     StringToNum(copy(MDest, 1, pos('/', MDest) - 1), MDestNet);
  603.                     StringToNum(copy(MOrig, pos('/', MOrig) + 1, 255), MOrigNode);
  604.                     StringToNum(copy(MOrig, 1, pos('/', MOrig) - 1), MOrigNet);
  605.                     TheTop[1] := chr(2);                {    Msg Type    }
  606.                     TheTop[2] := chr(0);                {    Msg Type    }
  607.                     TheTop[3] := chr(MOrigNode mod 256);    {    Origin    }
  608.                     TheTop[4] := chr(MOrigNode div 256);    {    Origin    }
  609.                     TheTop[5] := chr(MDestNode mod 256);    {    Destin    }
  610.                     TheTop[6] := chr(MDestNode div 256);    {    Destin    }
  611.                     TheTop[7] := chr(MOrigNet mod 256);    {    Origin    }
  612.                     TheTop[8] := chr(MOrigNet div 256);    {    Origin    }
  613.                     TheTop[9] := chr(MDestNet mod 256);    {    Destin    }
  614.                     TheTop[10] := chr(MDestNet div 256);    {    Destin    }
  615.                     TheTop[11] := chr(0);                {    Attribute    }
  616.                     TheTop[12] := chr(0);                {    Attribute    }
  617.                     TheTop[13] := chr(0);                {    Cost        }
  618.                     TheTop[14] := chr(0);                {    Cost        }
  619.                     TheTop[0] := chr(14);
  620.  
  621.                     TheTop := concat(TheTop, MDate);
  622.  
  623.                     if length(MTo) > 35 then
  624.                         MTo := copy(MTo, 1, 35);
  625.                     TheTop := concat(TheTop, MTo, chr(0));
  626.  
  627.                     if length(MFrom) > 35 then
  628.                         MFrom := copy(MFrom, 1, 35);
  629.                     TheTop := concat(TheTop, MFrom, chr(0));
  630.  
  631.                     if length(MSub) > 71 then
  632.                         MSub := copy(MSub, 1, 71);
  633.                     TheTop := concat(TheTop, MSub, chr(0));
  634.  
  635.                     TheLength := length(TheTop);
  636.  
  637.                     Err := FSWrite(FileRef, TheLength, Pointer(ord(@TheTop) + 1));    { Skip length byte }
  638.                 end
  639.         end;
  640.  
  641. {-----------------------------------------------------------------    }
  642.  
  643.         var
  644.             FromPoint, PointID: longint;
  645.  
  646.     begin
  647.         HeaderSize := SizeOf(Header);
  648.         TheHeader.MsgNo := maxlongint;
  649.         CharsToSend := HeaderSize;
  650.  
  651.         Err := FSOpen(concat(MsgPath, 'MSGHDR'), VRefNum, MHdrRef);
  652.         FindMHPosition;
  653.  
  654.         while (not AtEOF(MHdrRef)) do
  655.             begin
  656.                 if MultiFinder then
  657.                     IgnoreBool := WaitNextEvent(EveryEvent, TabbyEventRec, sleep, nil);
  658.                 Err := FSRead(MHdrRef, CharsToSend, @TheHeader);
  659.  
  660.                 TheStatus := MsgCategory[TheHeader.Section[1]];
  661.                 if (BitAnd(TabbyFlag, TheHeader.Status[1]) <> TabbyFlag) then
  662.                     if BitAnd(TheHeader.Status[1], Active) = Undeleted then            {    is it active?    }
  663.                         if ((TheStatus = LOCALPRIV) & DoLocPriv) | ((TheStatus = NETPRIV) & DoNetPriv) then
  664.                             begin
  665.                                 TempTo := TheHeader.MsgTo;
  666.                                 CleanString(TempTo);
  667.                                 for UserCount := 1 to LastEntry do
  668.                                     if EqualString(TheAddress[UserCount]^^.Name, TempTo, false, false) then
  669.                                         begin
  670.                                             FromPoint := 0;
  671.                                             PointID := 0;
  672.                                             TempSubj := TheHeader.MsgSubject;
  673.                                             TempFrom := TheHeader.MsgFrom;
  674.                                             DeCap(TempFrom);
  675.                                             DeCap(TempTo);
  676.                                             if (TheStatus = LOCALPRIV) then
  677.                                                 begin
  678.                                                     if length(TempFrom) <= (30 - length(PrivMark)) then        {MsgFrom can be 30 chars}
  679.                                                         TempFrom := concat(TempFrom, PrivMark)
  680.                                                     else                            {MsgSubject can be 40 chars}
  681.                                                         TempSubj := concat(copy(TempSubj, 1, (40 - length(PrivMark))), PrivMark);
  682.                                                     TheHeader.Dest := LocalNodeID        { i.e. '102/823' }
  683.                                                 end;
  684.                                             if (TheHeader.Dest = '') then
  685.                                                 TheHeader.Dest := LocalNodeID;        { i.e. '102/823' }
  686.  
  687.                                             Password := '';
  688.                                             Err := FSOpen(concat(gDefaultpath, 'Tabby:Password'), VRefNum, PWRef);
  689.                                             if Err = NoErr then
  690.                                                 while not AtEOF(PWRef) do
  691.                                                     begin
  692.                                                         Err := ReadALine(PWRef, TempString);
  693.                                                         if (pos(LocalNodeID, TempString) > 0) & (pos(Tab, TempString) > 0) then
  694.                                                             begin
  695.                                                                 Password := copy(TempString, pos(Tab, TempString) + 1, 255);
  696.                                                                 CleanString(Password);
  697.                                                                 Leave
  698.                                                             end
  699.                                                     end;
  700.                                             Err := FSClose(PWRef);
  701.  
  702.                                             if pos('.', TheHeader.Dest) > 0 then
  703.                                                 begin
  704.                                                     StringToNum(copy(TheHeader.Dest, pos('.', TheHeader.Dest) + 1, 255), FromPoint);
  705.                                                     TheHeader.Dest := copy(TheHeader.Dest, 1, pos('.', TheHeader.Dest) - 1)
  706.                                                 end;
  707.  
  708.                                             TempNode := TheAddress[UserCount]^^.Location;
  709.                                             if pos('.', TempNode) > 0 then
  710.                                                 begin
  711.                                                     StringToNum(copy(TempNode, pos('.', TempNode) + 1, 255), PointID);
  712.                                                     TempNode := copy(TempNode, 1, pos('.', TempNode) - 1)
  713.                                                 end;
  714.                                             MakeTextFile(concat(GenericPath, 'sendmail', TempNode, '.bbs'));
  715.                                             Err := FSopen(concat(GenericPath, 'sendmail', TempNode, '.bbs'), VRefNum, SendRef);
  716.                                             if Err = NoErr then
  717.                                                 Err := GetEOF(SendRef, TempLong);
  718.  
  719.                                             if (Err = NoErr) & (TempLong < 62) then        { Empty, write new        }
  720.                                                 begin
  721.                                                     WritePacketHeader(TempNode, SendRef);
  722.                                                     MakeTextFile(concat(gDefaultpath, 'Tabby:Compress Mail'));    { Since new, set to compress }
  723.                                                     Err := FSOpen(concat(gDefaultpath, 'Tabby:Compress Mail'), VRefNum, CompressRef);
  724.                                                     Err := SetFPos(CompressRef, FSFromLEOF, 0);    { Go to end    }
  725.                                                     Err := WrLn(CompressRef, concat('sendmail', TempNode, '.bbs'));
  726.                                                     Err := FSClose(CompressRef)
  727.                                                 end
  728.                                             else if Err = NoErr then                    { Header w/ messages    }
  729.                                                 Err := SetFPos(SendRef, FSFromLEOF, -2);    { Overwrite 00 00 bytes    }
  730.  
  731.                                             TempTime := MakeTime(0, ' ', TheHeader.TimeSent);
  732.                                             TempTime := concat(TempTime, '  ', MakeTime(3, ':', TheHeader.TimeSent), NULL);
  733.  
  734. {    procedure form is WriteMessageTop(MDest, MOrig, MDate, MTo, MFrom, MSub,FRef    )            }
  735.  
  736.                                             WriteMessageTop(TempNode, TheHeader.Dest, TempTime, TempTo, TempFrom, TempSubj, SendRef);
  737.  
  738.                                             if DEBUG then
  739.                                                 Err := WrLn(SendRef, concat('Debug info <To> <From> <LocalNodeID> <', TempNode, '> <', TheHeader.Dest, '> <', LocalNodeID, '>', ENDLINE)); {debug}
  740.  
  741.                                             Err := FSOpen(concat(MsgPath, 'MSGTXT'), VRefNum, MSGTXTRef);
  742.                                             Err := SetFPos(MSGTXTRef, fsFromStart, TheHeader.BeginText);
  743.                                             TheTextHandle := MTextHandle(NewHandle(sizeOf(MText)));
  744.                                             Err := FSRead(MSGTXTRef, TheHeader.LengthText, Ptr(TheTextHandle^));
  745.  
  746.                                             if (PointID > 0) then
  747.                                                 begin
  748.                                                     TempString := concat(CTLA, 'TOPT ', stringof(PointID : 1));
  749.                                                     Err := WrLn(SendRef, TempString)
  750.                                                 end;
  751.  
  752.                                             if (FromPoint > 0) then
  753.                                                 begin
  754.                                                     TempString := concat(CTLA, 'FMPT ', stringof(FromPoint : 1));
  755.                                                     Err := WrLn(SendRef, TempString)
  756.                                                 end;
  757.  
  758. {    Message text is in Pascal string form. Need to convert it to ASCII text.    }
  759.  
  760.                                             for Count := 1 to TheHeader.LengthText do
  761.                                                 begin
  762.                                                     NextCount := integer(TheTextHandle^^[Count]);
  763.                                                     if Count > 1 then
  764.                                                         TheTextHandle^^[Count] := ENDLINE;
  765.                                                     Count := Count + NextCount
  766.                                                 end;
  767.  
  768. {    Get rid of first length byte                }
  769.  
  770.                                             CharsToSend := TheHeader.LengthText - 1;
  771.                                             if (CharsToSend < 1) then
  772.                                                 begin
  773.                                                     TheTextHandle^^[1] := chr(9);
  774.                                                     TheTextHandle^^[2] := ENDLINE;
  775.                                                     CharsToSend := 2
  776.                                                 end;
  777.                                             for Count := 1 to CharsToSend do
  778.                                                 TheTextHandle^^[Count] := TheTextHandle^^[Count + 1];
  779.  
  780.                                             Err := FSWrite(SendRef, CharsToSend, Ptr(TheTextHandle^));
  781.  
  782.                                             TempString := '';
  783.                                             for Count := (CharsToSend - 100) to CharsToSend do
  784.                                                 TempString := concat(TempString, TheTextHandle^^[Count]);
  785.  
  786.                                             DisposHandle(Handle(TheTextHandle));
  787.  
  788.                                             Err := WrLn(SendRef, ENDLINE);
  789.                                             if pos('--- ff', TempString) = 0 then
  790.                                                 Err := WrLn(SendRef, concat('--- ff ', VERSION));
  791.                                             Err := Wr(SendRef, NULL);                {    End of message    }
  792.                                             Err := Wr(SendRef, concat(NULL, NULL));        {    End of file    }
  793.                                             Err := FSClose(MSGTXTRef);
  794.                                             Err := FSClose(SendRef);
  795.  
  796.                                             TheHeader.Status[1] := BitOr(Deleted, TheHeader.Status[1]);    {Set Delete Bit    }
  797.                                             CharsToSend := sizeOf(TheHeader);
  798.                                             Err := SetFPos(MHdrRef, fsFromMark, -CharsToSend);    {    Back up to the start of this record    }
  799.                                             Err := FSWrite(MHdrRef, CharsToSend, @TheHeader);
  800.                                             leave
  801.                                         end        {    if TheAddress[UserCount]^^.Name = TempTo    }
  802.                             end        {    if TheStatus = LOCALPRIV or NETPRIV    }
  803.  
  804.             end;    {    while (not AtEOF(MHdrRef))    }
  805.  
  806.         Err := FSClose(MHdrRef)
  807.     end;
  808.  
  809. {-----------------------------------------------------------------    }
  810.  
  811. procedure Initialize;
  812.  
  813.     var
  814.         PointNetID, GenericID, ConfigID: integer;
  815.  
  816.     begin
  817.         CurrentResFile := CurResFile;
  818.         ParamText(VERSION, '', '', '');
  819.         PrivMark := GetString(499)^^;
  820.         Defaults := GetString(502)^^;
  821.         if length(Defaults) < 2 then
  822.             Defaults := 'YY';
  823.         if Defaults[1] = 'Y' then
  824.             DoLocPriv := true
  825.         else
  826.             DoLocPriv := false;
  827.         if Defaults[2] = 'Y' then
  828.             DoNetPriv := true
  829.         else
  830.             DoNetPriv := false;
  831.  
  832.         Err := HGetVol(@gVolName, vRefNum, dirID);        { Get volume ref # & dirID for default volume    }
  833.         gDefaultpath := PathNameFromDirID(dirID, vRefNum);    { Get full pathname                            }
  834.  
  835.         Err := FSOpen(concat(gDefaultpath, 'Tabby:Point Net'), vRefNum, PointNetID);
  836.         if Err = NoErr then
  837.             begin
  838.                 Err := ReadALine(PointNetID, PointNet);
  839.                 Err := FSClose(PointNetID)
  840.             end
  841.         else
  842.             PointNet := '';
  843.         Err := FSOpen(concat(gDefaultpath, 'Generic'), vRefNum, GenericID);
  844.         if Err = NoErr then
  845.             begin
  846.                 Err := ReadALine(GenericID, GenericPath);
  847.                 Err := FSClose(GenericID)
  848.             end
  849.         else
  850.             GenericPath := '';
  851.  
  852.         OrigNode := 0;
  853.         OrigNet := 0;
  854.         Err := FSOpen(concat(gDefaultpath, 'Tabby:Tabby Config'), vRefNum, ConfigID);
  855.         if Err = NoErr then
  856.             begin
  857.                 Err := ReadALine(ConfigID, LocalNodeID);
  858.                 Err := FSClose(ConfigID);
  859.                 if pos(':', LocalNodeID) > 0 then
  860.                     LocalNodeID := copy(LocalNodeID, pos(':', LocalNodeID) + 1, 255);
  861.                 if pos('/', LocalNodeID) > 0 then
  862.                     begin
  863.                         StringToNum(copy(LocalNodeID, pos('/', LocalNodeID) + 1, 255), OrigNode);
  864.                         StringToNum(copy(LocalNodeID, 1, pos('/', LocalNodeID) - 1), OrigNet)
  865.                     end
  866.             end
  867.         else
  868.             LocalNodeID := ''
  869.     end;
  870.  
  871. {-----------------------------------------------------------------    }
  872.  
  873. procedure ShowMainDialog;
  874.  
  875.  
  876.     begin
  877.         DialogPointer := GetNewDialog(500, nil, POINTER(-1));
  878.         DrawDialog(DialogPointer);
  879.         SetPort(DialogPointer);
  880.     end;
  881.  
  882. {-----------------------------------------------------------------    }
  883.  
  884. procedure CleanUp;
  885.  
  886.     var
  887.         Counter: integer;
  888.  
  889.     begin
  890.         for Counter := 1 to LastEntry do
  891.             DisposHandle(Handle(TheAddress[Counter]));
  892.         DisposDialog(DialogPointer)
  893.     end;
  894.  
  895. { ------------------------------------------------------ }
  896.  
  897. procedure HandleConfig;
  898.  
  899.     var
  900.         LastHiMsgString: str255;
  901.         theDialog: DialogPtr;
  902.         ItemHit, itemType, whichItem, MsgRefNum: integer;
  903.         itemHandle: Handle;
  904.         dispRect: Rect;
  905.         thisButton: ControlHandle;
  906.         where: point;
  907.         CharsToSend, HiMsgNumber: longint;
  908.         fileReply: SFReply;
  909.         whatToFind: SFTypeList;
  910.  
  911.     begin
  912.         InitCursor;
  913.         ParamText(concat('v. ', VERSION), '', '', '');
  914.         theDialog := GetNewDialog(501, nil, POINTER(-1));
  915.         SetPort(theDialog);
  916.         FrameDItem(theDialog, Ok);
  917.  
  918.         NextLaunch := GetString(500)^^;        {    Get next launch string from resource    }
  919.         getDItem(theDialog, 3, itemType, itemHandle, dispRect);
  920.         SetIText(Handle(itemHandle), NextLaunch);
  921.  
  922.         getDItem(theDialog, 5, itemType, itemHandle, dispRect);
  923.         thisButton := ControlHandle(itemHandle);
  924.         if DoLocPriv then
  925.             SetCtlValue(thisButton, 1)
  926.         else
  927.             SetCtlValue(thisButton, 0);
  928.  
  929.         getDItem(theDialog, 6, itemType, itemHandle, dispRect);
  930.         thisButton := ControlHandle(itemHandle);
  931.         if DoNetPriv then
  932.             SetCtlValue(thisButton, 1)
  933.         else
  934.             SetCtlValue(thisButton, 0);
  935.  
  936.         if StillDown then
  937.             repeat
  938.             until not Button;
  939.         repeat
  940.             ModalDialog(nil, ItemHit);
  941.  
  942.             case ItemHit of
  943.                 1: { OK button hit -- save resources }
  944.                     begin
  945.                         getDItem(theDialog, 3, itemType, itemHandle, dispRect);
  946.                         GetIText(Handle(itemHandle), NextLaunch);
  947.                         RmveResource(GetResource('STR ', 500));
  948.                         UpdateResFile(CurrentResFile);
  949.                         AddResource(Handle(NewString(NextLaunch)), 'STR ', 500, 'Next Launch');
  950.  
  951.                         RmveResource(GetResource('STR ', 502));
  952.                         UpdateResFile(CurrentResFile);
  953.                         AddResource(Handle(NewString(Defaults)), 'STR ', 502, 'Defaults')
  954.                     end;
  955.  
  956.                 2: 
  957.                     ; { Cancel button hit—do nothing    }
  958.  
  959.  
  960.                 4: 
  961.                     if ButtonSelected(theDialog, 4) then
  962.                         begin { Look Up Next Launch button        }
  963.                             where.h := 60;
  964.                             where.v := 80;
  965.                             whatToFind[0] := 'APPL';
  966.                             ParamText('default application to launch', '', '', '');
  967.                             SFGETFile(where, '', nil, 1, whatToFind, nil, fileReply);
  968.                             if fileReply.good then
  969.                                 begin
  970.                                     getDItem(theDialog, 3, itemType, itemHandle, dispRect);
  971.                                     SetIText(Handle(itemHandle), fileReply.fName)
  972.                                 end;
  973.                             FrameDItem(theDialog, Ok)
  974.                         end;
  975.  
  976.                 5: 
  977.                     begin
  978.                         DoLocPriv := not DoLocPriv;
  979.                         getDItem(theDialog, 5, itemType, itemHandle, dispRect);
  980.                         thisButton := ControlHandle(itemHandle);
  981.                         if DoLocPriv then
  982.                             begin
  983.                                 SetCtlValue(thisButton, 1);
  984.                                 Defaults[1] := 'Y'
  985.                             end
  986.                         else
  987.                             begin
  988.                                 SetCtlValue(thisButton, 0);
  989.                                 Defaults[1] := 'N'
  990.                             end
  991.                     end;
  992.  
  993.                 6: 
  994.                     begin
  995.                         DoNetPriv := not DoNetPriv;
  996.                         getDItem(theDialog, 6, itemType, itemHandle, dispRect);
  997.                         thisButton := ControlHandle(itemHandle);
  998.                         if DoNetPriv then
  999.                             begin
  1000.                                 SetCtlValue(thisButton, 1);
  1001.                                 Defaults[2] := 'Y'
  1002.                             end
  1003.                         else
  1004.                             begin
  1005.                                 SetCtlValue(thisButton, 0);
  1006.                                 Defaults[2] := 'N'
  1007.                             end
  1008.                     end;
  1009.  
  1010.                 otherwise
  1011.                     ;    {    do nothing    }
  1012.  
  1013.             end;
  1014.         until (ItemHit = OK) or (ItemHit = 2);
  1015.         DisposDialog(theDialog)
  1016.     end;
  1017.  
  1018. { ------------------------------------------------------ }
  1019.  
  1020. begin
  1021.     Initialize;
  1022.     if Button then
  1023.         HandleConfig
  1024.     else
  1025.         begin
  1026.             ShowMainDialog;
  1027.             HelloTabby;
  1028.             ReadSettings;
  1029.             ReadConfig;
  1030.             ReadMESSAGES;
  1031.             ProcessHeaders;
  1032.             CleanUp;
  1033.             if NextLaunch <> '' then
  1034.                 LaunchNextAppl
  1035.         end
  1036. end.